/**
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

package pittypat.data;

import java.beans.PropertyVetoException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.TreeMap;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import pittypat.DataList;
import pittypat.DataModel;
import pittypat.FunctionArgs;
import pittypat.FunctionMeta;
import pittypat.FunctionModel;
import pittypat.IAsyncProgress;
import pittypat.ParameterMeta;
import pittypat.configuration.ConfigException;

/**
 * 针对数据库的 IDataService 抽象实现。
 */
public abstract class DbService extends DataService {
	private String connectionString;

	/**
	 * 初始化 DbDataService 类的新实例。
	 */
	protected DbService(String connectionString) {
		super();
		if (connectionString == null || connectionString.length() == 0) {
			throw new IllegalArgumentException("没有提供有效的连接字符串。");
		}

		this.connectionString = connectionString;
	}

	/**
	 * 为指定的命令创建一个参数。
	 * 
	 * @param statement
	 *            目标命令。
	 * @param meta
	 *            参数的元数据信息。
	 * @param position
	 *            参数的位置。
	 * @param args
	 *            参数值的列表。
	 * @throws SQLException
	 */
	protected abstract void createParameter(CallableStatement statement, ParameterMeta meta, int position,
			FunctionArgs args) throws SQLException;

	/**
	 * 注册一个输出参数。
	 * 
	 * @param statement
	 *            目标命令。
	 * @param meta
	 *            参数的元数据信息。
	 * @param position
	 *            参数的位置。
	 * @throws SQLException
	 */
	private void regOutParameter(CallableStatement statement, ParameterMeta meta, int position) throws SQLException {
		statement.registerOutParameter(position, this.getSqlType(meta));
	}

	/**
	 * 注册 _success 和 _message 输出参数。
	 * 
	 * @param statement
	 *            目标命令。
	 * @param position
	 *            参数的位置。
	 * @throws SQLException
	 */
	protected abstract void regSysParameters(CallableStatement statement, int position) throws SQLException;

	/**
	 * 获取指定参数的数据类型代码。
	 * 
	 * @param meta
	 *            参数的元数据信息。
	 * @return 数据库数据类型代码。
	 */
	protected abstract int getSqlType(ParameterMeta meta);

	/**
	 * 读取复杂类型输出参数的值。
	 * 
	 * @param reader
	 *            结果集合读取器。
	 * @param meta
	 *            参数的元数据。
	 * @param position
	 *            参数的位置。
	 * @param args
	 *            参数集合。
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 * @throws IllegalArgumentException
	 */
	private void readOutParameter(DbDataReader reader, ParameterMeta meta, int position, FunctionArgs args)
			throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
			NoSuchMethodException, SecurityException {
		if (DataModel.class.isAssignableFrom(meta.getDataType())) {
			// DataModel
			if (position == 1) {
				// 第一个结果集合
				if (reader.read()) {
					DataModel dm = (DataModel) args.getParameterValue(meta.getIdentifier());
					if (dm == null) {
						dm = (DataModel) meta.getDataType().newInstance();
						args.setParameterValue(meta.getIdentifier(), dm);
					}

					dm.read(reader);
				}
			} else {
				// 其他结果集合
				if (reader.nextResult() && reader.read()) {
					DataModel dm = (DataModel) args.getParameterValue(meta.getIdentifier());
					if (dm == null) {
						dm = (DataModel) meta.getDataType().newInstance();
						args.setParameterValue(meta.getIdentifier(), dm);
					}

					dm.read(reader);
				}
			}
		} else {
			// DataList
			if (position == 1) {
				// 第一个结果集合
				DataList<?> list = (DataList<?>) args.getParameterValue(meta.getIdentifier());
				if (list == null) {
					list = (DataList<?>) (meta.getDataType().getConstructor(meta.getItemType().getClass()))
							.newInstance(meta.getItemType());
					args.setParameterValue(meta.getIdentifier(), list);
				}

				list.read(reader);
			} else {
				// 其他结果集合
				if (reader.nextResult()) {
					DataList<?> list = (DataList<?>) args.getParameterValue(meta.getIdentifier());
					if (list == null) {
						list = (DataList<?>) (meta.getDataType().getConstructor(meta.getItemType().getClass()))
								.newInstance(meta.getItemType());
						args.setParameterValue(meta.getIdentifier(), list);
					}

					list.read(reader);
				}
			}
		}
	}

	/**
	 * 设置基础类型输出参数的值。
	 * 
	 * @param statement
	 *            目标数据库命令。
	 * @param meta
	 *            参数的元数据。
	 * @param position
	 *            参数的位置。
	 * @param args
	 *            参数集合。
	 * @throws SQLException
	 */
	protected abstract void setOutParameter(CallableStatement statement, ParameterMeta meta, int position,
			FunctionArgs args) throws SQLException;

	/**
	 * 设置 _success 和 _message 输出参数的值。
	 * 
	 * @param statement
	 *            目标命令。
	 * @param position
	 *            参数的位置。
	 * @param model
	 *            功能模型。
	 * @throws SQLException
	 */
	protected abstract void setSysParameterValues(CallableStatement statement, int position, FunctionModel<?> model)
			throws SQLException;

	/**
	 * 创建一个新的 DbDataReader 对象。
	 * 
	 * @param statement
	 *            目标数据库命令。
	 * @return 一个 DbDataReader 对象实例。
	 * @throws SQLException
	 */
	protected abstract DbDataReader createDataReader(CallableStatement statement) throws SQLException;

	// ComboPooledDataSource 连接池的字典集合，字典的键为连接字符串
	private static final TreeMap<String, ComboPooledDataSource> pools = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
	// 同步对连接池的访问
	private static final Object POOLS_LOCK = new Object();

	/**
	 * 在当前数据服务上执行指定的功能数据操作。
	 * 
	 * @param model
	 *            功能模型对象，其中包含了功能的参数列表、执行状态以及相关消息。
	 * @param progress
	 *            跟踪和汇报异步操作的进度。
	 * @throws ConfigException
	 *             配置错误。
	 * @throws SQLException
	 * @throws UnsupportedEncodingException
	 * @throws PropertyVetoException
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 * 
	 * @throws IllegalArgumentException
	 *             model 为 null，或没有找到目标功能的元数据信息。
	 */
	@Override
	public <T extends FunctionArgs> void exec(FunctionModel<T> model, IAsyncProgress progress)
			throws ConfigException, SQLException, UnsupportedEncodingException, PropertyVetoException,
			InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
			NoSuchMethodException, SecurityException {
		if (model == null) {
			throw new IllegalArgumentException("model 为 null。");
		}

		// 功能的元数据信息
		FunctionMeta meta = FunctionMeta.find(model.getArgs().getClass());
		if (meta == null) {
			throw new ConfigException("没有找到 " + model.getArgs().getClass().getName() + " 对应的功能配置信息。");
		}

		// 创建命令文本
		String commandText = meta.getSpCommandText();

		// 指示是否需要启动事务
		boolean transaction = meta.getAnnotation().transaction();
		// 参数列表
		FunctionArgs args = model.getArgs();
		// 数据库命令
		CallableStatement statement = null;
		// 用于读取结果集合的读取器
		DbDataReader reader = null;
		// 数据库连接上原来指示是否自动提交的值，用于支持本地事务
		boolean oldAutoCommit = true;

		// 根据模块的连接字符串获取或创建连接
		Connection conn = getConnection(this.connectionString, meta);
		if (conn == null) {
			throw new ConfigException(String.format("无法为模块 %s.%s.%s 创建数据库连接。", meta.getAppConfig().getDeveloper(),
					meta.getAppConfig().getIdentifier(), meta.getModuleConfig().getIdentifier()));
		}

		// 通过 try finally 确保连接被关闭
		try {
			if (transaction) {
				// 记录原始值
				oldAutoCommit = conn.getAutoCommit();
				// 禁用自动提交
				conn.setAutoCommit(false);
			}

			// 准备命令
			statement = conn.prepareCall(commandText);

			// 基础类型参数列表
			ParameterMeta[] parameters = meta.getPrimitiveParameters();
			if (parameters.length > 0) {
				// 设置输入参数的值，以及注册输出参数
				for (int i = 0; i < parameters.length; ++i) {
					if (parameters[i].isOut()) {
						this.regOutParameter(statement, parameters[i], i + 1);
					}
					
					if (parameters[i].isIn()) {
						this.createParameter(statement, parameters[i], i + 1, args);
					}
				}
			}
			
			// _context 参数
			statement.setString(parameters.length + 1, model.getContext() == null ? "" : model.getContext().toString());

			// 注册 _success 和 _message 参数
			this.regSysParameters(statement, parameters.length + 2);

			// 执行命令
			boolean hasResultSets = statement.execute();

			if (hasResultSets) {
				// 设置复杂类型输出参数的值
				parameters = meta.getComplexParameters();
				if (parameters.length > 0) {
					// 封装为 DbDataReader
					reader = this.createDataReader(statement);

					// 使用指定的处理程序处理 reader
					for (int i = 0; i < parameters.length; ++i) {
						this.readOutParameter(reader, parameters[i], i + 1, args);
					}
				}
			}

			// 设置基础类型输出参数的值
			parameters = meta.getPrimitiveParameters();
			if (parameters.length > 0) {
				for (int i = 0; i < parameters.length; ++i) {
					if (parameters[i].isOut()) {
						this.setOutParameter(statement, parameters[i], i + 1, args);
					}
				}
			}

			// 设置 _success 和 _message 参数的值
			this.setSysParameterValues(statement, parameters.length + 2, model);

			if (transaction) {
				// 提交事务
				conn.commit();
			}
		} catch (Exception exc) {
			if (transaction) {
				// 回滚事务
				conn.rollback();
			}
			throw exc;
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (Exception e) {}
				reader = null;
			}

			if (statement != null) {
				try {
					statement.close();
				} catch (Exception e) {}
				statement = null;
			}

			if (transaction) {
				// 还原是否自动提交事务的指示
				conn.setAutoCommit(oldAutoCommit);
			}

			// 关闭连接
			conn.close();
			conn = null;
		}
	}

	// 根据模块的配置信息获取或创建一个数据库连接
	private static Connection getConnection(String connectionString, FunctionMeta meta)
			throws SQLException, UnsupportedEncodingException, PropertyVetoException {
		synchronized (POOLS_LOCK) {
			// 首先从字典中查找已经创建的连接池
			ComboPooledDataSource pool = pools.getOrDefault(connectionString, null);
			if (pool == null) {
				// 创建新的连接池
				pool = createPool(
						String.format("%s.%s.%s", meta.getAppConfig().getDeveloper(),
								meta.getAppConfig().getIdentifier(), meta.getModuleConfig().getIdentifier()),
						connectionString);

				if (pool != null) {
					pools.put(connectionString, pool);
				}
			}

			return pool.getConnection();
		}
	}

	// 创建一个新的连接池
	private static ComboPooledDataSource createPool(String path, String connectionString)
			throws UnsupportedEncodingException, PropertyVetoException, SQLException {
		if (connectionString == null || connectionString.length() == 0) {
			return null;
		}

		ComboPooledDataSource pool = null;
		if (connectionString.startsWith("jdbc:mysql://")) {
			// MySQL
			pool = new ComboPooledDataSource();

			pool.setDriverClass("com.mysql.cj.jdbc.Driver");
			pool.setJdbcUrl(connectionString);
		} else if (connectionString.startsWith("jdbc:sqlserver://")) {
			// SQL SERVER
			pool = new ComboPooledDataSource();

			pool.setDriverClass("com.microsoft.sqlserver.jdbc.SQLServerDriver");
			pool.setJdbcUrl(connectionString);
		} else {
			throw new SQLException("暂时不支持除了 MySQL 和 SQL SERVER 之外的其他数据库");
		}

		return pool;
	}

	/**
	 * 在应用程序退出时关闭所有的连接池，释放相关的资源。
	 */
	public static void closeConnectionPools() {
		synchronized (POOLS_LOCK) {
			for (Map.Entry<String, ComboPooledDataSource> e : pools.entrySet()) {
				e.getValue().close();
			}

			pools.clear();
		}
	}
}
